#!/bin/sh
# the next line restarts using wish \
exec tclsh86 "$0" ${1+"$@"}

###############################################################################
#Batch convert pdf files to jpg images
#Copyright (C) 2010  Serban Teodorescu
#
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program.  If not, see <http://www.gnu.org/licenses/>.
###############################################################################

if { [ catch { package require fileutil } caughtError ] } {
    set missingLib "tcllib"
    puts "ERROR: $caughtError ...\n
          possibly missing library $missingLib ..."
    showRequiredLibs $missingLib
}
package require cmdline
package require struct::queue
package require log

if { [ catch { package require Thread } caughtError ] } {
    set missingLib "tclthread"
    puts "ERROR: $caughtError ...\n
          possibly missing library $missingLib ..."
    showRequiredLibs $missingLib 
}

# procedures
proc showRequiredLibs { missingLib } {
    puts "This application will not run unless the $missingLib library \n
          is installed. \n
          To install $missingLib: \n
          \t\* On Linux invoke your software management application and \n
          install the $missingLib package. For example, on a Fedora \n
          installation execute as root:\n
          \t \"yum install tcllib\"\n
          \t\* On Windows make sure you have installed Tcl downloaded from \n
          http://www.activestate.com."
    exit 1
}

proc searchFilesRecurse { dir ext { debug 0 } { progress "#" } } {
    
    global listFiles
    
    if { $debug eq 0 } {
            puts -nonewline $progress
    }
    
    set listLocalFiles [ lsort -dictionary [ glob -nocomplain -directory $dir $ext ] ] 
    if { [llength $listLocalFiles ] && $debug} {
        puts "\nINFO: found [ llength $listLocalFiles ] \
              [ regsub -all {\*|\.} $ext {} ] files in directory $dir"
    }
            
	foreach file [ glob -nocomplain $dir/* ] {
        
        if { [ file isdirectory $file ] } {
            if { $debug } {
                puts "DEBUG: searching [file normalize [file nativename $file]]..."
            }
            
            searchFilesRecurse $file $ext
            continue
        }
        
        if { [ string equal -nocase [ regsub -all {\*} $ext {} ] [ file extension $file ] ] } {
            lappend listFiles [ file normalize $file ]
            continue
        }
    }
}

proc inputChecks { dir } {

    if { [ file isdirectory $dir ] } {
        puts "\nINFO: Using search path starting from $dir ..."
        return -code ok 1
    } else {
        puts "\nINFO: Invalid command line parameter(s) detected.\n\
              $dir is not a valid directory or does not exist..."
        
        return -code ok 0
    }
}

proc Usage {} {

    puts "Usage:\n\
          [info script] \[Directoy where to start searching\] \[Extension of the files to search for\]\n\
          where\n\
          \[Directory where to start searching\] is a valid file system directory; \
          use forward slashes only.\n\
          and\n\
          \[Extension of the files to search for\] is a valid Windows style file extension; \
          formats like \"*.ext\", \".ext\", \"ext\" are acceptable and the length of the \
          extension is not limited to 3 characters.\n"
}

proc createGsOutput { myFile } {

    if { [ file isdir [ file normalize [ file rootname [ file normalize \
         [ file nativename $myFile ] ] ] ] ] } {
        return -code ok 1
    }
        
    if { ![file exists [file normalize [file rootname [file normalize [file nativename $myFile]]]]] || \
       { [file exists [file normalize [file rootname [file normalize [file nativename $myFile]]]]] && \
         [file isfile [file normalize [file rootname [file normalize [file nativename $myFile]]]]] \
       } } {
       
        if {[catch {file mkdir [file normalize [file rootname [file normalize [file nativename $myFile]]]]} catchMe]} {
            return -code error \
                   -errorInfo "[file nativename $myFile]: can't create output directory, $catchMe" \
                   0
        } else {
            return -code ok 1
        }
    }
}

proc execGs { gsBinary myFile { debug 0 } { fDebug } fExcludeFucked } {

    if { [ catch \
         { exec -ignorestderr -- $gsBinary \
                -dBATCH \
                -dNOPAUSE \
                -sDEVICE=jpeg \
                -dJPEGQ=100 \
                -sOutputFile=[ file normalize [ file rootname [ file normalize \
                             [ file nativename $myFile ] ] ] ]\\page\-\%03d.jpg \
                [ file normalize [ file nativename $myFile ] ] \
                >& "[ file normalize [ file rootname [ file normalize \
                    [ file nativename $myFile ] ] ] ]\\conversion.log" \
         } catchMe ] \
        } {
            if { $debug } {
                puts $fDebug "[ file normalize [ file nativename $myFile ] ]: gs error caught $catchMe\?"
            }
            puts $fExcludeFucked $myFile
            flush $fExcludeFucked
        
            return -code ok 0
    } else {
        return -code ok 1
    }
}

# globals
global listFiles

# configuration file using java style .properties format ######################
# gsBinary = 
# debug = 
# logLevel = 
# searchRootPath =
# ext =
# progressChar = 
# excludeConvertedFileName =
# excludeFuckedFileName =
# logFileNamePrefixPattern =
# logFileName =
# #############################################################################

# default absolute path for configuration file
# $HOME/.[info scriptname]/[info scriptname].config on unix style systems
# 
# input args
# be careful with the globals, they are not globals
global gsBinary

# globals that should not be globals but need to be available in execGs
# the ghostscript interpreter tends to fail in ways that cannot be handled
# via catch and we use these globals to log such failures
global fDebug
global fExcludeFucked
global debug
global progress

set searchRootPath "D:/hentai"
set ext "\*\.pdf"

set debug 1

# char to use when showing search progress, only used if debug is not enabled
set progress "\."


# Start main

# dealing with the ghostscript binary
# later on this will be loaded from a configuration file but for now let's
# hard-code it and do the checks
set gsLinBinary "/usr/bin/gs"
set gsWinBinary "\/gs\*\/gswin32c.exe"
# hack for version 0.0.1
if { [string match $tcl_platform(platform) "unix"] } {
    set gsBinary "/usr/bin/gs"
}
if {[string match $tcl_platform(platform) "windows"]} {
    set gsBinary "C:/Program Files/gs/gs8.70/bin/gswin32c.exe"
}
if { [string match $tcl_platform(platform) "unix" ] } {
    if { ![ ::fileutil::test $gsLinBinary efx catchGsBinaryError ] } {
	puts "ERROR: there is a problem with the ghostscript binary $gsLinBinary: \
	      $catchGsBinaryError"
	exit 1
    } else {
	set gsBinary $gsLinBinary
    }
}

# on windows we are interested in the following environment variables:
# %PROGRAMV6432%, %PROGRAMFILES(X86)%, %PROGRAMFILES%
# note that this approach will not work on a system with software installed on
# more than one drive (the famous D:\Program Files problem). This will also not
# work if ghostscript is installed in a non-default location like C:\gs.
# The workaround would be to actually search all the drives for ghostscript
# installations but... oh, baby. what if the user has like 4Tbytes of hard
# drives? Maybe try a few choice locations and then give up?
## ignore this line for the moment: home, programw6432, userprofile (mayeb), programfiles(x86), localappdata maybe
if { [ string match $tcl_platform(platform) "windows" ] } {
    
}
set fnExcludeConverted "\./converted.log"
set fnExcludeFucked "\./fucked.log"

set listFiles [ list ]
set listExcludeConverted ""
set listExcludeFucked ""
set countErrors 0
# set gsBinary "C:/Program Files/gs/gs8.70/bin/gswin32c.exe"

# Verify and process input
switch -exact $argc {
    0 {
        puts "\nNo command line parameters detected.\n\
              Using default search path starting from $searchRootPath ...\n\
              Using default search extension $ext ..."
    }
    
    1 {
        if { ![ inputChecks [ lindex $argv 0 ] ] } {
            Usage
            exit
        }
        
        set searchRootPath [ lindex $argv 0 ]
        puts "\nUsing default search extension $ext ..."
    }
    
    default {
        if { ![ inputChecks [ lindex $argv 0 ] ] } {
            Usage
            exit
        }
        
        set searchRootPath [ lindex $argv 0 ]
        set ext "\*\.[ regsub -all {\*|\.|\?} [ lindex $argv 1 ] {} ]"
        puts "\nUsing search extension $ext ..."
    }
}
            

# begin

searchFilesRecurse $searchRootPath $ext

if { [ file isfile $fnExcludeConverted ] } {
    set fExcludeConverted [ open $fnExcludeConverted r ]
    set content [ read -nonewline $fExcludeConverted ]
    set listExcludeConverted [ split $content \n ]
    puts "\nINFO: Exclude converted files listed in \
          [ file normalize [ file nativename $fnExcludeConverted ] ]..."
    close $fExcludeConverted
}
      
if { $debug } {
    puts "\nDEBUG: all the [ regsub -all {\*|\.} $ext {} ] files \
          successfully converted during this session will have their names \
          appended to the \
          [ file normalize [ file nativename $fnExcludeConverted ] ] file..."
}

if { [ file isfile $fnExcludeFucked ] } {      
    set fExcludeFucked [ open $fnExcludeFucked r ]
    set content [ read -nonewline $fExcludeFucked ]
    set listExcludeFucked [ split $content "\n" ]
    puts "\nINFO: Exclude failed conversion files list in \
              [ file normalize [ file nativename $fnExcludeFucked ] ]:\n\t \
              Some of these files can be unfucked the cutePDF writer..."
    close $fExcludeFucked
}

if { $debug } {
    puts "\nDEBUG: all the [ regsub -all {\*|\.} $ext {} ] files \
          that failed conversion during this session will have their names \
          appended to the \
          [ file normalize [ file nativename $fnExcludeFucked ] ] file. \
          Be sure to remove the ones \
          run through cutePDF for another conversion attempt..."
}
      
if { $debug } {
    set fDebug [ open "\./[clock format [clock seconds] -format {%y-%m-%d--%H-%M-%S}]-listFiles.log" w ]
    puts $fDebug "--- Excluded from conversion, already converted ---"
    puts $fDebug [ join $listExcludeConverted \n ]
    puts $fDebug "\n--- Excluded from conversion, fucked-up pdf formatting ---"
    puts $fDebug [ join $listExcludeFucked \n ]
    puts $fDebug "\--- Attempt conversion during this session ---"
    puts $fDebug [ join $listFiles \n ]
    puts $fDebug "\n--- Failures ---"
    flush $fDebug
    # close $fDebug
    puts "\nDEBUG: List of files to convert available in the \
            [ file normalize [ file rootname [ file normalize [ file nativename \
            [ info script ] ] ] ] ]\/debug\.log file..."
}

if { ![ llength $listFiles ] } {
    puts "\nINFO: 0 [ regsub -all {\*\.} $ext {} ] files \
          under directory $searchRootPath and its subdirectories"
    exit 0
} else {
    puts "\nINFO: [ llength $listFiles ] \
          [ regsub -all {\*\.} $ext {} ] files..."
    set countSkipped 0
    set countRemaining [ llength $listFiles ]
}    

set fExcludeConverted [ open $fnExcludeConverted a+ ]
set fExcludeFucked [ open $fnExcludeFucked a+ ]
                   
foreach myFile $listFiles {

    if { [ lsearch -exact -sorted -increasing -dictionary $listExcludeConverted \
         $myFile ] != -1 } {
        puts "\nDEBUG: skipping $myFile, already converted..."
        puts "INFO: [ incr countSkipped ] files..."
        puts "INFO: [ incr countRemaining -1 ] files to convert..."
        continue
    }
    
    if { [ lsearch -exact -sorted -increasing -dictionary $listExcludeFucked $myFile ] ne -1 } {
        puts "\nDEBUG: skipping $myFile, conversion failed once already..."
        puts "INFO: [ incr countSkipped ] files..."
        puts "INFO: [ incr countRemaining -1 ] files to convert..."
        continue
    }
    
    puts "\nINFO: [ file nativename $myFile ]: Now converting..."
            
    if { ![createGsOutput $myFile ] } {
        puts "INFO: [ file nativename $myFile ]: $errorInfo. aborting conversion..."
        puts "\nINFO: [ incr countErrors ] conversion errors..."
        puts "INFO: [ incr countRemaining -1 ] files to convert..."
        if { $debug } {
            puts $fDebug "[ file nativename $myFile ]: $errorInfo. aborting conversion..."
            flush $fDebug
        }
        continue
    }
    
    if { ![ execGs $myFile ] } {
        puts "\nINFO: [ file nativename $myFile ]: $errorInfo. aborting conversion..."
        puts "\nINFO: [ incr countErrors ] conversion errors..."
        puts "INFO: [ incr countRemaining -1 ] files still to convert..."
        if { $debug } {
            puts $fDebug "[ file nativename $myFile ]: $errorInfo. aborting conversion..."
            flush $fDebug
        }
        continue
    }
    
    puts "\nINFO: [ file nativename $myFile ]: converted to JPEG images in \
          [ file nativename [ file rootname $myFile ] ]..."
          
    puts $fExcludeConverted $myFile
    flush $fExcludeConverted
          
    puts "\nINFO: [ expr { [ llength $listFiles ] - [ incr countRemaining -1 ] } ] files processed..."
    puts "INFO: [ expr { [ llength $listFiles ] - $countRemaining - $countErrors } ] files converted..."
    puts "INFO: $countErrors files failed conversions so far..."
    puts "INFO: $countSkipped files skipped..."
    puts "INFO: $countRemaining files to convert..."
    
}

if { $debug } {
    close $fDebug
}

close $fExcludeConverted
close $fExcludeFucked

puts "\nINFO: done. converted [ expr { [ llength $listFiles ] - $countErrors -$countSkipped } ] \
        [ regsub -all {\*|\.} $ext {} ] files to jpeg images"
puts "INFO: $countSkipped files skipped..."
puts "INFO: $countErrors [ regsub -all {\*|\.} $ext {} ] file conversions failed..."

exit 0

